/* eslint-disable max-classes-per-file */
import BaseXform from '../base-xform';

import ColorXform from './color-xform';

class StopXform extends BaseXform {
    constructor() {
        super();

        this.map = {
            color: new ColorXform(),
        };
    }

    get tag() {
        return 'stop';
    }

    render(xmlStream, model) {
        xmlStream.openNode('stop');
        xmlStream.addAttribute('position', model.position);
        this.map.color.render(xmlStream, model.color);
        xmlStream.closeNode();
    }

    parseOpen(node) {
        if (this.parser) {
            this.parser.parseOpen(node);
            return true;
        }
        switch (node.name) {
            case 'stop':
                this.model = {
                    position: parseFloat(node.attributes.position),
                };
                return true;
            case 'color':
                this.parser = this.map.color;
                this.parser.parseOpen(node);
                return true;
            default:
                return false;
        }
    }

    parseText() {
    }

    parseClose(name) {
        if (this.parser) {
            if (!this.parser.parseClose(name)) {
                this.model.color = this.parser.model;
                this.parser = undefined;
            }
            return true;
        }
        return false;
    }
}

class PatternFillXform extends BaseXform {
    constructor() {
        super();

        this.map = {
            fgColor: new ColorXform('fgColor'),
            bgColor: new ColorXform('bgColor'),
        };
    }

    get name() {
        return 'pattern';
    }

    get tag() {
        return 'patternFill';
    }

    render(xmlStream, model) {
        xmlStream.openNode('patternFill');
        xmlStream.addAttribute('patternType', model.pattern);
        if (model.fgColor) {
            this.map.fgColor.render(xmlStream, model.fgColor);
        }
        if (model.bgColor) {
            this.map.bgColor.render(xmlStream, model.bgColor);
        }
        xmlStream.closeNode();
    }

    parseXmlObject(node) {
        if (node._name === 'patternFill') {
            this.model = {
                type: 'pattern',
                pattern: node._attributes.patternType,
            };
            node._elements?.forEach((item) => {
                this.parser = this.map[item._name];
                if (this.parser && this.parser.parseXmlObject(item)) {
                    if (this.parser.model) {
                        this.model[item._name] = this.parser.model;
                    }
                    this.parser = undefined;
                }
            })
            return true;
        }
    }

    parseOpen(node) {
        if (this.parser) {
            this.parser.parseOpen(node);
            return true;
        }
        switch (node.name) {
            case 'patternFill':
                this.model = {
                    type: 'pattern',
                    pattern: node.attributes.patternType,
                };
                return true;
            default:
                this.parser = this.map[node.name];
                if (this.parser) {
                    this.parser.parseOpen(node);
                    return true;
                }
                return false;
        }
    }

    parseText(text) {
        if (this.parser) {
            this.parser.parseText(text);
        }
    }

    parseClose(name) {
        if (this.parser) {
            if (!this.parser.parseClose(name)) {
                if (this.parser.model) {
                    this.model[name] = this.parser.model;
                }
                this.parser = undefined;
            }
            return true;
        }
        return false;
    }
}

class GradientFillXform extends BaseXform {
    constructor() {
        super();

        this.map = {
            stop: new StopXform(),
        };
    }

    get name() {
        return 'gradient';
    }

    get tag() {
        return 'gradientFill';
    }

    render(xmlStream, model) {
        xmlStream.openNode('gradientFill');
        switch (model.gradient) {
            case 'angle':
                xmlStream.addAttribute('degree', model.degree);
                break;
            case 'path':
                xmlStream.addAttribute('type', 'path');
                if (model.center.left) {
                    xmlStream.addAttribute('left', model.center.left);
                    if (model.center.right === undefined) {
                        xmlStream.addAttribute('right', model.center.left);
                    }
                }
                if (model.center.right) {
                    xmlStream.addAttribute('right', model.center.right);
                }
                if (model.center.top) {
                    xmlStream.addAttribute('top', model.center.top);
                    if (model.center.bottom === undefined) {
                        xmlStream.addAttribute('bottom', model.center.top);
                    }
                }
                if (model.center.bottom) {
                    xmlStream.addAttribute('bottom', model.center.bottom);
                }
                break;

            default:
                break;
        }

        const stopXform = this.map.stop;
        model.stops.forEach(stopModel => {
            stopXform.render(xmlStream, stopModel);
        });

        xmlStream.closeNode();
    }

    parseOpen(node) {
        if (this.parser) {
            this.parser.parseOpen(node);
            return true;
        }
        switch (node.name) {
            case 'gradientFill': {
                const model = (this.model = {
                    stops: [],
                });
                if (node.attributes.degree) {
                    model.gradient = 'angle';
                    model.degree = parseInt(node.attributes.degree, 10);
                } else if (node.attributes.type === 'path') {
                    model.gradient = 'path';
                    model.center = {
                        left: node.attributes.left ? parseFloat(node.attributes.left) : 0,
                        top: node.attributes.top ? parseFloat(node.attributes.top) : 0,
                    };
                    if (node.attributes.right !== node.attributes.left) {
                        model.center.right = node.attributes.right ? parseFloat(node.attributes.right) : 0;
                    }
                    if (node.attributes.bottom !== node.attributes.top) {
                        model.center.bottom = node.attributes.bottom ? parseFloat(node.attributes.bottom) : 0;
                    }
                }
                return true;
            }

            case 'stop':
                this.parser = this.map.stop;
                this.parser.parseOpen(node);
                return true;

            default:
                return false;
        }
    }

    parseText(text) {
        if (this.parser) {
            this.parser.parseText(text);
        }
    }

    parseClose(name) {
        if (this.parser) {
            if (!this.parser.parseClose(name)) {
                this.model.stops.push(this.parser.model);
                this.parser = undefined;
            }
            return true;
        }
        return false;
    }
}

// Fill encapsulates translation from fill model to/from xlsx
class FillXform extends BaseXform {
    constructor() {
        super();

        this.map = {
            patternFill: new PatternFillXform(),
            gradientFill: new GradientFillXform(),
        };
    }

    get tag() {
        return 'fill';
    }

    render(xmlStream, model) {
        xmlStream.addRollback();
        xmlStream.openNode('fill');
        switch (model.type) {
            case 'pattern':
                this.map.patternFill.render(xmlStream, model);
                break;
            case 'gradient':
                this.map.gradientFill.render(xmlStream, model);
                break;
            default:
                xmlStream.rollback();
                return;
        }
        xmlStream.closeNode();
        xmlStream.commit();
    }

    parseXmlObject(node) {
        if (node._name === 'fill') {
            node._elements.forEach((item) => {
                this.parser = this.map[item._name];
                if (this.parser && this.parser.parseXmlObject(item)) {
                    this.model = this.parser.model;
                    this.model.type = this.parser.name;
                    this.parser = undefined;
                }
            })
            return true;
        }
        return false;
    }

    parseOpen(node) {
        if (this.parser) {
            this.parser.parseOpen(node);
            return true;
        }
        switch (node.name) {
            case 'fill':
                this.model = {};
                return true;
            default:
                this.parser = this.map[node.name];
                if (this.parser) {
                    this.parser.parseOpen(node);
                    return true;
                }
                return false;
        }
    }

    parseText(text) {
        if (this.parser) {
            this.parser.parseText(text);
        }
    }

    parseClose(name) {
        if (this.parser) {
            if (!this.parser.parseClose(name)) {
                this.model = this.parser.model;
                this.model.type = this.parser.name;
                this.parser = undefined;
            }
            return true;
        }
        return false;
    }

    validStyle(value) {
        return FillXform.validPatternValues[value];
    }
}

FillXform.validPatternValues = [
    'none',
    'solid',
    'darkVertical',
    'darkGray',
    'mediumGray',
    'lightGray',
    'gray125',
    'gray0625',
    'darkHorizontal',
    'darkVertical',
    'darkDown',
    'darkUp',
    'darkGrid',
    'darkTrellis',
    'lightHorizontal',
    'lightVertical',
    'lightDown',
    'lightUp',
    'lightGrid',
    'lightTrellis',
    'lightGrid',
].reduce((p, v) => {
    p[v] = true;
    return p;
}, {});

FillXform.StopXform = StopXform;
FillXform.PatternFillXform = PatternFillXform;
FillXform.GradientFillXform = GradientFillXform;

export default FillXform;
